package pl.poznan.put.cs.to.tsp.localsearch;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import pl.poznan.put.cs.to.tsp.common.Vertexes;

public class VertexTransferHandler implements IMoveHandler {
	private List<Integer> a;
	private List<Integer> b;
	private boolean left;
	private int first;
	private boolean improved;
	private Vertexes vertexes;
	private Random r;
	private TabuManager tabuManager;

	public VertexTransferHandler(List<Integer> a, List<Integer> b,
			Vertexes vertexes, TabuManager tabuManager) {
		super();
		this.a = a;
		this.b = b;
		this.vertexes = vertexes;
		this.r = new Random();
		this.tabuManager = tabuManager;

		//System.out.println(">BEFORE<");
		//System.out.println("A:");
		//this.printList(this.a);
		//System.out.println("B:");
		//this.printList(this.b);

		this.improved = this.tryVertexTransfer();

		//System.out.println(">AFTER<");
		///System.out.println("A:");
		//this.printList(this.a);
		//System.out.println("B:");
		//this.printList(this.b);
	}

	// private void printList(List<Integer> list) {
	// for (int i : list) {
	// System.out.print(i + " ");
	// }
	// //System.out.println();
	// //System.out.println("n = " + list.size());
	// }

	private boolean tryVertexTransfer() {
		this.first = this.r.nextInt(this.a.size() + this.b.size());
		this.left = this.r.nextBoolean();
		boolean possible;

		int actual = this.first;
		int tabu = -1;
		int tabuDelta = Integer.MAX_VALUE;
		do {
			if (this.a.contains(actual)) {
				possible = (this.a.size() > 1);
			} else {
				possible = (this.b.size() > 1);
			}
			if (possible) {
				int delta = this.simulateTransfer(actual);
				if (delta < 0) {
					if (this.tabuManager.isTabuVertex(Integer.valueOf(actual))) {
						if (tabu == -1) {
							tabu = actual;
							tabuDelta = delta;
						} else {
							if (delta < tabuDelta) {
								tabu = actual;
								tabuDelta = delta;
							}
						}
					} else {
						this.preformMove(actual);
						this.improved = true;

						this.tabuManager.add(actual);

						return true;
					}
				}
			}
			if (this.left) {
				actual = (actual - 1 + this.a.size() + this.b.size())
						% (this.a.size() + this.b.size());
			} else {
				actual = (actual + 1) % (this.a.size() + this.b.size());
			}
			// System.out.println("actual -> " + actual + " / "
			// + (this.a.size() + this.b.size()));
		} while (actual != this.first);

		if (tabu != -1) {
			this.preformMove(tabu);
			this.improved = true;

			this.tabuManager.add(actual);

			return true;
		}

		return false;
	}

	private void preformMove(int vIndex) {
		int newPred = this.findBestPlaceToPut(vIndex);

		if (this.a.contains(vIndex)) {
			this.a = this.createListWithout(this.a, vIndex);
			this.b = this.createListWith(this.b, vIndex, newPred);
		} else {
			this.b = this.createListWithout(this.b, vIndex);
			this.a = this.createListWith(this.a, vIndex, newPred);
		}
	}

	private ArrayList<Integer> createListWithout(List<Integer> list, int vIndex) {
		ArrayList<Integer> res = new ArrayList<Integer>();
		int i = list.indexOf(vIndex);
		if (i == 0) {
			res.addAll(list.subList(1, list.size()));
		} else if (i == list.size() - 1) {
			res.addAll(list.subList(0, list.size() - 1));
		} else {
			res.addAll(list.subList(0, i));
			res.addAll(list.subList(i + 1, list.size()));
		}
		return res;
	}

	private ArrayList<Integer> createListWith(List<Integer> list, int vIndex,
			int pred) {
		ArrayList<Integer> res = new ArrayList<Integer>();
		int i = list.indexOf(pred);
		if (i == 0) {
			res.add(pred);
			res.add(vIndex);
			res.addAll(list.subList(1, list.size()));
		} else if (i == list.size() - 1) {
			res.add(vIndex);
			res.addAll(list);
		} else {
			res.addAll(list.subList(0, i + 1));
			res.add(vIndex);
			res.addAll(list.subList(i + 1, list.size()));
		}

		return res;
	}

	private int simulateTransfer(int vIndex) {
		int pred = this.findPredecessor(vIndex);
		int succ = this.findSuccessor(vIndex);

		int newPred = this.findBestPlaceToPut(vIndex);
		int newSucc = this.findSuccessor(newPred);

		int predSucc = this.vertexes.getDistanceBetween(pred, succ);
		int predV = this.vertexes.getDistanceBetween(pred, vIndex);
		int succV = this.vertexes.getDistanceBetween(succ, vIndex);
		int newPredNewSucc = this.vertexes.getDistanceBetween(newPred, newSucc);
		int newPredV = this.vertexes.getDistanceBetween(newPred, vIndex);
		int newSuccV = this.vertexes.getDistanceBetween(newSucc, vIndex);

		return ((predSucc + newPredV + newSuccV) - (predV + succV + newPredNewSucc));
	}

	private int findBestPlaceToPut(int vIndex) {
		List<Integer> x = null;
		if (this.a.contains(vIndex)) {
			x = this.b;
		} else {
			x = this.a;
		}

		if (x.size() == 0) {
			return 0;
		}

		int delta = Integer.MAX_VALUE;
		int result = -1;

		for (int i = 0; i < x.size(); i++) {
			int j = (i + 1) % x.size();
			int tmp = this.vertexes.getDistanceBetween(x.get(i), x.get(j))
					- (this.vertexes.getDistanceBetween(x.get(i), vIndex) + this.vertexes
							.getDistanceBetween(x.get(j), vIndex));
			if (tmp < delta) {
				delta = tmp;
				result = x.get(i);
			}
		}

		return result;
	}

	private int findPredecessor(int vIndex) {
		if (this.a.contains(vIndex)) {
			return this.a.get((this.a.indexOf(vIndex) - 1 + this.a.size())
					% this.a.size());
		} else {
			return this.b.get((this.b.indexOf(vIndex) - 1 + this.b.size())
					% this.b.size());
		}
	}

	private int findSuccessor(int vIndex) {
		if (this.a.contains(vIndex)) {
			return this.a.get((this.a.indexOf(vIndex) + 1) % this.a.size());
		} else {
			return this.b.get((this.b.indexOf(vIndex) + 1) % this.b.size());
		}
	}

	@Override
	public List<Integer> getA() {
		return this.a;
	}

	@Override
	public List<Integer> getB() {
		return this.b;
	}

	@Override
	public boolean hasImproved() {
		return this.improved;
	}
}
